Popular Searches
Popular Course Categories
Popular Courses

Navigator Push and Pop

Flutter Navigation & Screens

Flutter Navigator Push and Pop

Navigation is an essential part of almost every Flutter application. It allows users to move from one screen to another, open detail pages, complete multi-step workflows, and return to previously visited screens.

In Flutter, screens and pages are represented as routes. The Navigator manages these routes using a stack. Navigator.push() adds a new route to the stack, while Navigator.pop() removes the current route and reveals the previous route. Flutter Navigation Basics


1. What Is Navigator in Flutter?

Navigator is a Flutter widget that manages a stack of routes. It provides methods for moving between screens and controlling the navigation history of an application.

Think of the Navigator as a stack of pages:

Initial Screen
      ↓
Second Screen
      ↓
Third Screen

When a new screen is opened, it is placed on top of the stack. When the current screen is closed, the top route is removed and the previous screen becomes visible.


2. What Is a Route?

In Flutter, a screen or page is commonly represented as a Route. A route describes a screen that can be placed into the Navigator's navigation stack.

For example:

HomeScreen
ProductScreen
ProfileScreen
SettingsScreen

Each of these can participate in Flutter navigation as a route.

A MaterialPageRoute is commonly used in Material applications, while CupertinoPageRoute provides an iOS-style transition. Flutter Navigation Cookbook


3. Understanding the Navigation Stack

Flutter's Navigator works using a stack-based navigation model.

Suppose the application starts with the Home screen:

Stack:
[Home]

When the user opens the Products screen:

Stack:
[Home]
[Products]

When the user opens Product Details:

Stack:
[Home]
[Products]
[Product Details]

When Navigator.pop() is called:

Stack:
[Home]
[Products]

The Product Details route is removed and the Products screen becomes visible again.


4. Navigator.push()

Navigator.push() adds a new route to the Navigator's stack and displays that route above the current screen.

The basic syntax is:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SecondScreen(),
  ),
);

The Flutter API defines Navigator.push() as returning a Future. That Future completes when the pushed route is popped, optionally providing the value supplied to Navigator.pop(). Navigator.push API


5. Basic Navigator.push() Example

import 'package:flutter/material.dart';

void main() {
  runApp(
    const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomeScreen(),
    ),
  );
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const SecondScreen(),
              ),
            );
          },
          child: const Text('Open Second Screen'),
        ),
      ),
    );
  }
}

class SecondScreen extends StatelessWidget {
  const SecondScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Second Screen'),
      ),
      body: const Center(
        child: Text('Welcome to Second Screen'),
      ),
    );
  }
}

When the button is pressed, Flutter creates the second route and pushes it onto the navigation stack.


6. Breaking Down Navigator.push()

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SecondScreen(),
  ),
);
PartPurpose
NavigatorManages navigation routes.
push()Adds a new route to the stack.
contextIdentifies the Navigator associated with the widget tree.
MaterialPageRouteCreates a Material-style route.
builderBuilds the destination screen.
SecondScreen()The screen being opened.

7. Navigator.pop()

Navigator.pop() removes the current route from the navigation stack and returns the user to the previous route.

Basic syntax:

Navigator.pop(context);

The official Flutter navigation recipe uses Navigator.pop() to return from the second route to the first route. Navigate to a new screen and back


8. Basic Navigator.pop() Example

class SecondScreen extends StatelessWidget {
  const SecondScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Second Screen'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: const Text('Go Back'),
        ),
      ),
    );
  }
}

When the button is pressed, the current route is removed from the stack.


9. Push and Pop Together

push() and pop() are normally used together.

Home
  |
  | Navigator.push()
  v
Details
  |
  | Navigator.pop()
  v
Home

This is one of the most basic navigation patterns in Flutter.


10. Complete Push and Pop Example

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const DetailsScreen(),
              ),
            );
          },
          child: const Text('Open Details'),
        ),
      ),
    );
  }
}

class DetailsScreen extends StatelessWidget {
  const DetailsScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Details'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: const Text('Go Back'),
        ),
      ),
    );
  }
}

11. Navigation Flow

Application starts
      ↓
HomeScreen
      ↓
User taps "Open Details"
      ↓
Navigator.push()
      ↓
DetailsScreen
      ↓
User taps "Go Back"
      ↓
Navigator.pop()
      ↓
HomeScreen

12. Using Navigator.of(context)

Instead of calling:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const DetailsScreen(),
  ),
);

You can explicitly retrieve the nearest Navigator:

Navigator.of(context).push(
  MaterialPageRoute(
    builder: (context) => const DetailsScreen(),
  ),
);

Similarly:

Navigator.of(context).pop();

Navigator.of(context) retrieves the nearest Navigator associated with the supplied BuildContext. Flutter Stack-Based Navigation


13. MaterialPageRoute

MaterialPageRoute creates a route with Material Design page-transition behavior.

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const ProfileScreen(),
  ),
);

It is commonly used when building applications with MaterialApp.

Example

MaterialPageRoute(
  builder: (context) => const ProfileScreen(),
)

14. CupertinoPageRoute

For iOS-style navigation transitions, Flutter provides CupertinoPageRoute.

Navigator.push(
  context,
  CupertinoPageRoute(
    builder: (context) => const ProfileScreen(),
  ),
);

It can be useful when an application wants Cupertino-style page transitions.


15. Passing Data with Navigator.push()

Navigation often requires sending information from one screen to another. For example, a product list can open a product details screen and pass the selected product.

Product Model

class Product {
  final String name;
  final double price;

  const Product({
    required this.name,
    required this.price,
  });
}

Passing the Product

final product = Product(
  name: 'Laptop',
  price: 60000,
);

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => ProductDetailsScreen(
      product: product,
    ),
  ),
);

Receiving the Product

class ProductDetailsScreen extends StatelessWidget {
  final Product product;

  const ProductDetailsScreen({
    super.key,
    required this.product,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(product.name),
      ),
      body: Center(
        child: Text(
          'Price: ₹${product.price}',
        ),
      ),
    );
  }
}

Flutter's navigation cookbook documents passing objects to a new route, including passing data through a screen constructor. Send data to a new screen


16. Returning Data with Navigator.pop()

Navigator.pop() can also return a value to the previous screen.

For example:

Navigator.pop(context, 'Selected');

The previous screen can wait for the result:

final result = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SelectionScreen(),
  ),
);

print(result);

The Navigator API specifies that the Future returned by push() completes with the result passed to pop(). Navigator API


17. Complete Example of Returning Data

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State createState() => _HomeScreenState();
}

class _HomeScreenState extends State {
  String selectedValue = 'Nothing selected';

  Future openSelectionScreen() async {
    final result = await Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => const SelectionScreen(),
      ),
    );

    if (!mounted) return;

    setState(() {
      selectedValue = result ?? 'Nothing selected';
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(selectedValue),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: openSelectionScreen,
              child: const Text('Select Option'),
            ),
          ],
        ),
      ),
    );
  }
}

class SelectionScreen extends StatelessWidget {
  const SelectionScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Select Option'),
      ),
      body: Column(
        children: [
          ListTile(
            title: const Text('Flutter'),
            onTap: () {
              Navigator.pop(context, 'Flutter');
            },
          ),
          ListTile(
            title: const Text('Dart'),
            onTap: () {
              Navigator.pop(context, 'Dart');
            },
          ),
        ],
      ),
    );
  }
}

Flutter's official cookbook demonstrates this pattern for returning a selection from one screen to another. Return data from a screen


18. Navigator.push() with Generic Types

Flutter navigation methods can use generic types to describe the value returned when a route is popped.

final result = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SelectionScreen(),
  ),
);

Here, String indicates that the route can return a String value.

For a boolean result:

final result = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const ConfirmationScreen(),
  ),
);

19. Push Multiple Screens

You can push multiple routes sequentially.

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const ScreenA(),
  ),
);

Then from Screen A:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const ScreenB(),
  ),
);

The stack becomes:

[Home]
[Screen A]
[Screen B]

Calling:

Navigator.pop(context);

removes Screen B:

[Home]
[Screen A]

20. Multiple Pop Operations

Sometimes an application needs to go back through several screens. popUntil() can remove routes until a specified condition is satisfied.

Navigator.popUntil(
  context,
  (route) => route.isFirst,
);

This removes routes until the first route in the stack is reached.


21. Navigator.pushReplacement()

pushReplacement() pushes a new route and replaces the current route.

This can be useful for flows such as login to home.

Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) => const HomeScreen(),
  ),
);

Before:

[Login]

After:

[Home]

The Login route is replaced instead of remaining below Home.


22. Navigator.pushAndRemoveUntil()

pushAndRemoveUntil() adds a new route and removes previous routes until a condition is met.

For example, after completing checkout, you may want to navigate to the home screen and remove previous checkout pages:

Navigator.pushAndRemoveUntil(
  context,
  MaterialPageRoute(
    builder: (context) => const HomeScreen(),
  ),
  (route) => false,
);

The result is effectively:

Before:
Home
Cart
Checkout
Payment
Success

After:
Home

Flutter's navigation cookbook lists pushAndRemoveUntil, pushReplacement, popUntil, and other Navigator methods for more advanced stack manipulation. Flutter Navigation Basics


23. Navigator.popUntil()

popUntil() repeatedly removes routes until the supplied condition becomes true.

Navigator.popUntil(
  context,
  (route) => route.isFirst,
);

For example:

Home
Products
Product Details
Checkout

After popUntil(route.isFirst):

Home

24. Navigation After Login

A common application flow is:

Login
  ↓
Home
  ↓
Products
  ↓
Product Details

After successful authentication, the login screen may be replaced:

Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) => const HomeScreen(),
  ),
);

This prevents the user from returning to the login page through normal back navigation.


25. Navigation After Checkout

Suppose an e-commerce application has:

Home
 ↓
Products
 ↓
Cart
 ↓
Checkout
 ↓
Payment
 ↓
Order Success

After a successful order, the application may clear the checkout flow and return to Home:

Navigator.pushAndRemoveUntil(
  context,
  MaterialPageRoute(
    builder: (context) => const HomeScreen(),
  ),
  (route) => false,
);

26. Using Back Button with Navigator.pop()

Flutter's AppBar can automatically provide a back button when the current route has a previous route to return to.

You can also create your own back button:

IconButton(
  icon: const Icon(Icons.arrow_back),
  onPressed: () {
    Navigator.pop(context);
  },
)

Another example:

ElevatedButton.icon(
  onPressed: () {
    Navigator.pop(context);
  },
  icon: const Icon(Icons.arrow_back),
  label: const Text('Back'),
)

27. Checking Whether a Route Can Be Popped

Before manually popping a route, you may need to determine whether there is a previous route in the Navigator stack.

if (Navigator.canPop(context)) {
  Navigator.pop(context);
}

This can be useful when implementing custom navigation controls.


28. Navigator.push() vs pushReplacement()

MethodBehaviorTypical Use
push()Adds a new routeHome → Details
pushReplacement()Replaces current routeLogin → Home
pushAndRemoveUntil()Adds route and removes routes based on conditionCheckout → Home
pop()Removes current routeDetails → Home
popUntil()Removes routes until a condition is metReturn to first route

29. Navigator.push() vs Navigator.pop()

push()pop()
Adds a routeRemoves a route
Moves forwardMoves backward
Opens a new screenCloses the current screen
Returns a FutureCan return a result
Increases stack sizeDecreases stack size

30. Navigator Stack Example

Step 1:
[Home]

Step 2:
Navigator.push(Products)
[Home]
[Products]

Step 3:
Navigator.push(Details)
[Home]
[Products]
[Details]

Step 4:
Navigator.pop()
[Home]
[Products]

Step 5:
Navigator.pop()
[Home]

31. Practical Product Navigation Example

class ProductListScreen extends StatelessWidget {
  const ProductListScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Products'),
      ),
      body: ListView(
        children: [
          ListTile(
            leading: const Icon(Icons.phone_android),
            title: const Text('Smartphone'),
            subtitle: const Text('₹25,000'),
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => const ProductDetailsScreen(
                    name: 'Smartphone',
                    price: 25000,
                  ),
                ),
              );
            },
          ),
          ListTile(
            leading: const Icon(Icons.laptop),
            title: const Text('Laptop'),
            subtitle: const Text('₹60,000'),
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => const ProductDetailsScreen(
                    name: 'Laptop',
                    price: 60000,
                  ),
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}

class ProductDetailsScreen extends StatelessWidget {
  final String name;
  final double price;

  const ProductDetailsScreen({
    super.key,
    required this.name,
    required this.price,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Product Details'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              name,
              style: const TextStyle(
                fontSize: 28,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 10),
            Text(
              'Price: ₹$price',
              style: const TextStyle(
                fontSize: 20,
              ),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: const Text('Back to Products'),
            ),
          ],
        ),
      ),
    );
  }
}

32. Passing Objects Through Navigator.push()

For larger applications, passing a complete model object is often cleaner than passing many individual values.

class Course {
  final String title;
  final String description;
  final double price;

  const Course({
    required this.title,
    required this.description,
    required this.price,
  });
}

Push the course:

final course = Course(
  title: 'Flutter Training',
  description: 'Learn Flutter application development.',
  price: 15000,
);

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => CourseDetailsScreen(
      course: course,
    ),
  ),
);

33. Returning a Boolean Result

A common use case is asking the user to confirm an action.

final confirmed = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const ConfirmationScreen(),
  ),
);

if (confirmed == true) {
  print('User confirmed');
} else {
  print('User cancelled');
}

On the confirmation screen:

ElevatedButton(
  onPressed: () {
    Navigator.pop(context, true);
  },
  child: const Text('Confirm'),
)

Cancel button:

TextButton(
  onPressed: () {
    Navigator.pop(context, false);
  },
  child: const Text('Cancel'),
)

34. Important Difference Between pop() and pushReplacement()

Consider:

[Home]
[Login]

If you call:

Navigator.pop(context);

the Login route is removed and Home becomes visible.

If you call:

Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) => const HomeScreen(),
  ),
);

the current route is replaced with Home.

Choosing between them depends on the desired navigation history.


35. Common Mistakes with Navigator.push()

Mistake 1: Forgetting BuildContext

Navigation requires a valid BuildContext.

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const DetailsScreen(),
  ),
);

Mistake 2: Forgetting the Destination Screen

The route needs a widget to build:

builder: (context) => const DetailsScreen()

Mistake 3: Pushing the Same Screen Repeatedly

Repeatedly calling push() can create many duplicate routes in the navigation stack.

Mistake 4: Using pop() Without Considering the Stack

Before custom back navigation, you can check:

Navigator.canPop(context)

Mistake 5: Ignoring the Returned Future

If the destination screen returns information, await the Future returned by Navigator.push().


36. Navigator.push() and Async Programming

Because Navigator.push() returns a Future, it can be used with async and await.

Future openDetails() async {
  final result = await Navigator.push(
    context,
    MaterialPageRoute(
      builder: (context) => const DetailsScreen(),
    ),
  );

  print(result);
}

The Future completes when the destination route is popped.


37. Safe Context Usage After await

If an asynchronous navigation result is followed by a widget update, check that the widget is still mounted before using its context or state.

final result = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SelectionScreen(),
  ),
);

if (!mounted) return;

setState(() {
  selectedValue = result ?? '';
});

This helps avoid using a State object after it has been removed from the widget tree.


38. Navigator and Bottom Navigation

NavigationBar and Navigator can be used together.

For example:

Home
Products
Orders
Profile
   |
   +-- Settings
   +-- Edit Profile

The NavigationBar can switch between top-level sections, while Navigator.push() can open detail screens inside a section.

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SettingsScreen(),
  ),
);

39. Navigator and Forms

Navigation is frequently used when opening forms.

ElevatedButton(
  onPressed: () async {
    final result = await Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => const EditProfileScreen(),
      ),
    );

    if (!mounted) return;

    if (result != null) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(result),
        ),
      );
    }
  },
  child: const Text('Edit Profile'),
)

The form screen can return a message:

Navigator.pop(
  context,
  'Profile updated successfully',
);

40. Navigator and Dialogs

Some Flutter APIs such as dialogs and modal bottom sheets also use route-based mechanisms internally.

For example:

showDialog(
  context: context,
  builder: (context) {
    return AlertDialog(
      title: const Text('Delete Item?'),
      content: const Text(
        'Do you want to delete this item?',
      ),
      actions: [
        TextButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: const Text('Cancel'),
        ),
        TextButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: const Text('Delete'),
        ),
      ],
    );
  },
);

Here, Navigator.pop() dismisses the dialog route.


41. Named Routes and Navigator

Flutter supports named routes through Navigator.pushNamed().

MaterialApp(
  routes: {
    '/': (context) => const HomeScreen(),
    '/details': (context) => const DetailsScreen(),
  },
);

Navigate using:

Navigator.pushNamed(
  context,
  '/details',
);

Return using:

Navigator.pop(context);

Named routes remain part of Flutter, but current Flutter documentation does not recommend them for most new applications. For many new applications, Flutter recommends using Navigator with MaterialPageRoute or a routing package such as go_router, particularly when advanced routing or deep linking is required. Flutter Navigation and Routing


42. Navigator.push() with Named Routes

Navigator.pushNamed(
  context,
  '/profile',
);

This approach can be useful when working with an existing application that already uses named routes.

For new applications, direct route construction or a modern routing solution may be more appropriate depending on the application's requirements.


43. Nested Navigator

Large applications may contain more than one Navigator. A nested Navigator can manage navigation within a particular section or workflow.

Main Navigator
│
├── Home
├── Products
└── Profile
      │
      └── Nested Navigator
            ├── Profile Home
            ├── Edit Profile
            └── Settings

This approach can be useful when an individual section needs its own navigation history.


44. NavigatorObserver

NavigatorObserver can be used to observe navigation events such as routes being pushed or popped.

class MyNavigatorObserver extends NavigatorObserver {
  @override
  void didPush(Route route, Route? previousRoute) {
    print('Route pushed: ${route.settings.name}');
  }

  @override
  void didPop(Route route, Route? previousRoute) {
    print('Route popped: ${route.settings.name}');
  }
}

It can be registered with MaterialApp:

MaterialApp(
  navigatorObservers: [
    MyNavigatorObserver(),
  ],
  home: const HomeScreen(),
);

This can be useful for analytics, logging, and monitoring navigation behavior.


45. Practical E-Commerce Navigation Flow

Home
 |
 +-- Products
 |     |
 |     +-- Product Details
 |           |
 |           +-- Add to Cart
 |
 +-- Cart
 |     |
 |     +-- Checkout
 |           |
 |           +-- Payment
 |                 |
 |                 +-- Order Success
 |
 +-- Profile
       |
       +-- Edit Profile
       +-- Settings

Different Navigator operations can be used for different stages of this flow.

ActionNavigation Method
Open Product Detailspush()
Return to Productspop()
Login → HomepushReplacement()
Checkout → Home after completionpushAndRemoveUntil()
Return to first screenpopUntil()

46. Complete Mini Project

import 'package:flutter/material.dart';

void main() {
  runApp(const NavigationApp());
}

class NavigationApp extends StatelessWidget {
  const NavigationApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Navigator Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.blue,
        ),
        useMaterial3: true,
      ),
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'Welcome to the Application',
              style: TextStyle(
                fontSize: 22,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () async {
                final result = await Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) =>
                        const DetailsScreen(),
                  ),
                );

                if (!context.mounted) return;

                if (result != null) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                      content: Text(result),
                    ),
                  );
                }
              },
              child: const Text('Open Details'),
            ),
          ],
        ),
      ),
    );
  }
}

class DetailsScreen extends StatelessWidget {
  const DetailsScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Details'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'This is the Details Screen',
              style: TextStyle(fontSize: 20),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Navigator.pop(
                  context,
                  'Returned from Details Screen',
                );
              },
              child: const Text('Return Data'),
            ),
            const SizedBox(height: 10),
            TextButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: const Text('Go Back'),
            ),
          ],
        ),
      ),
    );
  }
}

47. Execution Flow of the Mini Project

  1. The application starts on HomeScreen.
  2. The user taps Open Details.
  3. Navigator.push() adds DetailsScreen to the stack.
  4. The Details screen appears.
  5. The user can press Go Back.
  6. Navigator.pop() removes the Details route.
  7. The user can also press Return Data.
  8. Navigator.pop(context, result) returns a String.
  9. The Future returned by Navigator.push() receives that String.
  10. The Home screen displays the returned result.

48. Important Navigator Methods

MethodDescription
push()Adds a route to the navigation stack.
pop()Removes the current route.
pushReplacement()Replaces the current route with another route.
pushAndRemoveUntil()Pushes a route and removes previous routes according to a condition.
popUntil()Pops routes until a condition is satisfied.
pushNamed()Pushes a route using a registered route name.
canPop()Checks whether the Navigator can pop a route.
of()Retrieves the nearest Navigator.

49. Common Interview Questions

Q1. What is Navigator in Flutter?

Navigator is a widget that manages a stack of routes and provides methods for navigating between screens.

Q2. What does Navigator.push() do?

It adds a new route to the Navigator stack and displays it above the current route.

Q3. What does Navigator.pop() do?

It removes the current route from the stack and reveals the previous route.

Q4. What is MaterialPageRoute?

It is a route implementation that provides Material-style page transitions.

Q5. Can Navigator.pop() return data?

Yes. A value can be supplied as the second argument:

Navigator.pop(context, result);

Q6. Can Navigator.push() receive returned data?

Yes. Navigator.push() returns a Future that completes when the route is popped.

Q7. What is the difference between push() and pop()?

push() adds a route, while pop() removes the current route.

Q8. What is pushReplacement()?

It replaces the current route with a new route.

Q9. What is pushAndRemoveUntil()?

It pushes a new route and removes previous routes until the supplied condition is satisfied.

Q10. What is popUntil()?

It removes routes until a specified condition becomes true.


50. Practice Exercises

  1. Create a Home screen and Details screen.
  2. Navigate from Home to Details using Navigator.push().
  3. Return to Home using Navigator.pop().
  4. Create a Product model and pass it to Product Details.
  5. Return a selected product from a selection screen.
  6. Create Login and Home screens and use pushReplacement().
  7. Create Home, Cart, Checkout, and Success screens.
  8. Use pushAndRemoveUntil() after checkout.
  9. Use popUntil() to return to the Home screen.
  10. Implement a confirmation screen that returns a Boolean result.
  11. Create a reusable navigation helper method.
  12. Experiment with MaterialPageRoute and CupertinoPageRoute.
  13. Implement a nested Navigator for a Profile section.
  14. Add a NavigatorObserver to log route changes.

51. Best Practices

  • Use Navigator.push() when adding a new screen to the navigation stack.
  • Use Navigator.pop() to return to the previous screen.
  • Use meaningful route structures for larger applications.
  • Pass strongly typed objects when practical.
  • Use generic types when returning values from routes.
  • Use pushReplacement() when the current route should no longer remain in the normal back stack.
  • Use pushAndRemoveUntil() for flows where previous routes should be removed.
  • Use popUntil() when returning to a specific point in the navigation stack.
  • Check mounted after awaiting navigation results before updating widget state.
  • Use nested navigation when a subsection requires an independent navigation flow.
  • For complex deep-linking requirements, consider Router-based navigation or a routing package such as go_router.

52. Quick Revision

ConceptMeaning
NavigatorManages navigation routes as a stack.
RouteRepresents a screen/page in navigation.
push()Adds a new route.
pop()Removes the current route.
pushReplacement()Replaces the current route.
pushAndRemoveUntil()Pushes a route and removes previous routes according to a condition.
popUntil()Pops routes until a condition is met.
MaterialPageRouteMaterial-style route.
CupertinoPageRouteCupertino-style route.
canPop()Checks whether the current Navigator can pop.
pushNamed()Pushes a registered named route.

53. Key Takeaways

  • Flutter uses a Navigator to manage a stack of routes.
  • Navigator.push() opens a new screen by adding a route to the stack.
  • Navigator.pop() closes the current screen by removing its route.
  • A route can return data through Navigator.pop(context, result).
  • The Future returned by Navigator.push() can receive that returned result.
  • MaterialPageRoute is commonly used for Material applications.
  • CupertinoPageRoute provides Cupertino-style transitions.
  • pushReplacement() is useful when replacing the current navigation entry.
  • pushAndRemoveUntil() is useful for clearing previous navigation history according to a condition.
  • popUntil() can return to an earlier point in the navigation stack.
  • Navigator can be combined with bottom navigation and nested navigation.
  • For advanced navigation and deep-linking requirements, Flutter supports Router-based navigation and routing packages.

54. Useful Resources

Flutter Navigation and Routing: Official Flutter Navigation Documentation

Navigator API: Official Navigator API Documentation

Navigate to a New Screen and Back: Flutter Navigation Basics

Passing Data: Send Data to a New Screen

Returning Data: Return Data from a Screen

JustAcademy Flutter Training: JustAcademy Flutter Training Course

Register for Flutter Course Demo: Register for Flutter Course Demo

Flutter Navigator Push and Pop

Navigation is an essential part of almost every Flutter application. It allows users to move from one screen to another, open detail pages, complete multi-step workflows, and return to previously visited screens.

In Flutter, screens and pages are represented as routes. The Navigator manages these routes using a stack. Navigator.push() adds a new route to the stack, while Navigator.pop() removes the current route and reveals the previous route. Flutter Navigation Basics


1. What Is Navigator in Flutter?

Navigator is a Flutter widget that manages a stack of routes. It provides methods for moving between screens and controlling the navigation history of an application.

Think of the Navigator as a stack of pages:

Initial Screen
      ↓
Second Screen
      ↓
Third Screen

When a new screen is opened, it is placed on top of the stack. When the current screen is closed, the top route is removed and the previous screen becomes visible.


2. What Is a Route?

In Flutter, a screen or page is commonly represented as a Route. A route describes a screen that can be placed into the Navigator's navigation stack.

For example:

HomeScreen
ProductScreen
ProfileScreen
SettingsScreen

Each of these can participate in Flutter navigation as a route.

A MaterialPageRoute is commonly used in Material applications, while CupertinoPageRoute provides an iOS-style transition. Flutter Navigation Cookbook


3. Understanding the Navigation Stack

Flutter's Navigator works using a stack-based navigation model.

Suppose the application starts with the Home screen:

Stack:
[Home]

When the user opens the Products screen:

Stack:
[Home]
[Products]

When the user opens Product Details:

Stack:
[Home]
[Products]
[Product Details]

When Navigator.pop() is called:

Stack:
[Home]
[Products]

The Product Details route is removed and the Products screen becomes visible again.


4. Navigator.push()

Navigator.push() adds a new route to the Navigator's stack and displays that route above the current screen.

The basic syntax is:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SecondScreen(),
  ),
);

The Flutter API defines Navigator.push() as returning a Future. That Future completes when the pushed route is popped, optionally providing the value supplied to Navigator.pop(). Navigator.push API


5. Basic Navigator.push() Example

import 'package:flutter/material.dart';

void main() {
  runApp(
    const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: HomeScreen(),
    ),
  );
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const SecondScreen(),
              ),
            );
          },
          child: const Text('Open Second Screen'),
        ),
      ),
    );
  }
}

class SecondScreen extends StatelessWidget {
  const SecondScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Second Screen'),
      ),
      body: const Center(
        child: Text('Welcome to Second Screen'),
      ),
    );
  }
}

When the button is pressed, Flutter creates the second route and pushes it onto the navigation stack.


6. Breaking Down Navigator.push()

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SecondScreen(),
  ),
);
PartPurpose
NavigatorManages navigation routes.
push()Adds a new route to the stack.
contextIdentifies the Navigator associated with the widget tree.
MaterialPageRouteCreates a Material-style route.
builderBuilds the destination screen.
SecondScreen()The screen being opened.

7. Navigator.pop()

Navigator.pop() removes the current route from the navigation stack and returns the user to the previous route.

Basic syntax:

Navigator.pop(context);

The official Flutter navigation recipe uses Navigator.pop() to return from the second route to the first route. Navigate to a new screen and back


8. Basic Navigator.pop() Example

class SecondScreen extends StatelessWidget {
  const SecondScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Second Screen'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: const Text('Go Back'),
        ),
      ),
    );
  }
}

When the button is pressed, the current route is removed from the stack.


9. Push and Pop Together

push() and pop() are normally used together.

Home
  |
  | Navigator.push()
  v
Details
  |
  | Navigator.pop()
  v
Home

This is one of the most basic navigation patterns in Flutter.


10. Complete Push and Pop Example

import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const DetailsScreen(),
              ),
            );
          },
          child: const Text('Open Details'),
        ),
      ),
    );
  }
}

class DetailsScreen extends StatelessWidget {
  const DetailsScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Details'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: const Text('Go Back'),
        ),
      ),
    );
  }
}

11. Navigation Flow

Application starts
      ↓
HomeScreen
      ↓
User taps "Open Details"
      ↓
Navigator.push()
      ↓
DetailsScreen
      ↓
User taps "Go Back"
      ↓
Navigator.pop()
      ↓
HomeScreen

12. Using Navigator.of(context)

Instead of calling:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const DetailsScreen(),
  ),
);

You can explicitly retrieve the nearest Navigator:

Navigator.of(context).push(
  MaterialPageRoute(
    builder: (context) => const DetailsScreen(),
  ),
);

Similarly:

Navigator.of(context).pop();

Navigator.of(context) retrieves the nearest Navigator associated with the supplied BuildContext. Flutter Stack-Based Navigation


13. MaterialPageRoute

MaterialPageRoute creates a route with Material Design page-transition behavior.

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const ProfileScreen(),
  ),
);

It is commonly used when building applications with MaterialApp.

Example

MaterialPageRoute(
  builder: (context) => const ProfileScreen(),
)

14. CupertinoPageRoute

For iOS-style navigation transitions, Flutter provides CupertinoPageRoute.

Navigator.push(
  context,
  CupertinoPageRoute(
    builder: (context) => const ProfileScreen(),
  ),
);

It can be useful when an application wants Cupertino-style page transitions.


15. Passing Data with Navigator.push()

Navigation often requires sending information from one screen to another. For example, a product list can open a product details screen and pass the selected product.

Product Model

class Product {
  final String name;
  final double price;

  const Product({
    required this.name,
    required this.price,
  });
}

Passing the Product

final product = Product(
  name: 'Laptop',
  price: 60000,
);

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => ProductDetailsScreen(
      product: product,
    ),
  ),
);

Receiving the Product

class ProductDetailsScreen extends StatelessWidget {
  final Product product;

  const ProductDetailsScreen({
    super.key,
    required this.product,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(product.name),
      ),
      body: Center(
        child: Text(
          'Price: ₹${product.price}',
        ),
      ),
    );
  }
}

Flutter's navigation cookbook documents passing objects to a new route, including passing data through a screen constructor. Send data to a new screen


16. Returning Data with Navigator.pop()

Navigator.pop() can also return a value to the previous screen.

For example:

Navigator.pop(context, 'Selected');

The previous screen can wait for the result:

final result = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SelectionScreen(),
  ),
);

print(result);

The Navigator API specifies that the Future returned by push() completes with the result passed to pop(). Navigator API


17. Complete Example of Returning Data

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State createState() => _HomeScreenState();
}

class _HomeScreenState extends State {
  String selectedValue = 'Nothing selected';

  Future openSelectionScreen() async {
    final result = await Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => const SelectionScreen(),
      ),
    );

    if (!mounted) return;

    setState(() {
      selectedValue = result ?? 'Nothing selected';
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(selectedValue),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: openSelectionScreen,
              child: const Text('Select Option'),
            ),
          ],
        ),
      ),
    );
  }
}

class SelectionScreen extends StatelessWidget {
  const SelectionScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Select Option'),
      ),
      body: Column(
        children: [
          ListTile(
            title: const Text('Flutter'),
            onTap: () {
              Navigator.pop(context, 'Flutter');
            },
          ),
          ListTile(
            title: const Text('Dart'),
            onTap: () {
              Navigator.pop(context, 'Dart');
            },
          ),
        ],
      ),
    );
  }
}

Flutter's official cookbook demonstrates this pattern for returning a selection from one screen to another. Return data from a screen


18. Navigator.push() with Generic Types

Flutter navigation methods can use generic types to describe the value returned when a route is popped.

final result = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SelectionScreen(),
  ),
);

Here, String indicates that the route can return a String value.

For a boolean result:

final result = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const ConfirmationScreen(),
  ),
);

19. Push Multiple Screens

You can push multiple routes sequentially.

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const ScreenA(),
  ),
);

Then from Screen A:

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const ScreenB(),
  ),
);

The stack becomes:

[Home]
[Screen A]
[Screen B]

Calling:

Navigator.pop(context);

removes Screen B:

[Home]
[Screen A]

20. Multiple Pop Operations

Sometimes an application needs to go back through several screens. popUntil() can remove routes until a specified condition is satisfied.

Navigator.popUntil(
  context,
  (route) => route.isFirst,
);

This removes routes until the first route in the stack is reached.


21. Navigator.pushReplacement()

pushReplacement() pushes a new route and replaces the current route.

This can be useful for flows such as login to home.

Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) => const HomeScreen(),
  ),
);

Before:

[Login]

After:

[Home]

The Login route is replaced instead of remaining below Home.


22. Navigator.pushAndRemoveUntil()

pushAndRemoveUntil() adds a new route and removes previous routes until a condition is met.

For example, after completing checkout, you may want to navigate to the home screen and remove previous checkout pages:

Navigator.pushAndRemoveUntil(
  context,
  MaterialPageRoute(
    builder: (context) => const HomeScreen(),
  ),
  (route) => false,
);

The result is effectively:

Before:
Home
Cart
Checkout
Payment
Success

After:
Home

Flutter's navigation cookbook lists pushAndRemoveUntil, pushReplacement, popUntil, and other Navigator methods for more advanced stack manipulation. Flutter Navigation Basics


23. Navigator.popUntil()

popUntil() repeatedly removes routes until the supplied condition becomes true.

Navigator.popUntil(
  context,
  (route) => route.isFirst,
);

For example:

Home
Products
Product Details
Checkout

After popUntil(route.isFirst):

Home

24. Navigation After Login

A common application flow is:

Login
  ↓
Home
  ↓
Products
  ↓
Product Details

After successful authentication, the login screen may be replaced:

Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) => const HomeScreen(),
  ),
);

This prevents the user from returning to the login page through normal back navigation.


25. Navigation After Checkout

Suppose an e-commerce application has:

Home
 ↓
Products
 ↓
Cart
 ↓
Checkout
 ↓
Payment
 ↓
Order Success

After a successful order, the application may clear the checkout flow and return to Home:

Navigator.pushAndRemoveUntil(
  context,
  MaterialPageRoute(
    builder: (context) => const HomeScreen(),
  ),
  (route) => false,
);

26. Using Back Button with Navigator.pop()

Flutter's AppBar can automatically provide a back button when the current route has a previous route to return to.

You can also create your own back button:

IconButton(
  icon: const Icon(Icons.arrow_back),
  onPressed: () {
    Navigator.pop(context);
  },
)

Another example:

ElevatedButton.icon(
  onPressed: () {
    Navigator.pop(context);
  },
  icon: const Icon(Icons.arrow_back),
  label: const Text('Back'),
)

27. Checking Whether a Route Can Be Popped

Before manually popping a route, you may need to determine whether there is a previous route in the Navigator stack.

if (Navigator.canPop(context)) {
  Navigator.pop(context);
}

This can be useful when implementing custom navigation controls.


28. Navigator.push() vs pushReplacement()

MethodBehaviorTypical Use
push()Adds a new routeHome → Details
pushReplacement()Replaces current routeLogin → Home
pushAndRemoveUntil()Adds route and removes routes based on conditionCheckout → Home
pop()Removes current routeDetails → Home
popUntil()Removes routes until a condition is metReturn to first route

29. Navigator.push() vs Navigator.pop()

push()pop()
Adds a routeRemoves a route
Moves forwardMoves backward
Opens a new screenCloses the current screen
Returns a FutureCan return a result
Increases stack sizeDecreases stack size

30. Navigator Stack Example

Step 1:
[Home]

Step 2:
Navigator.push(Products)
[Home]
[Products]

Step 3:
Navigator.push(Details)
[Home]
[Products]
[Details]

Step 4:
Navigator.pop()
[Home]
[Products]

Step 5:
Navigator.pop()
[Home]

31. Practical Product Navigation Example

class ProductListScreen extends StatelessWidget {
  const ProductListScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Products'),
      ),
      body: ListView(
        children: [
          ListTile(
            leading: const Icon(Icons.phone_android),
            title: const Text('Smartphone'),
            subtitle: const Text('₹25,000'),
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => const ProductDetailsScreen(
                    name: 'Smartphone',
                    price: 25000,
                  ),
                ),
              );
            },
          ),
          ListTile(
            leading: const Icon(Icons.laptop),
            title: const Text('Laptop'),
            subtitle: const Text('₹60,000'),
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => const ProductDetailsScreen(
                    name: 'Laptop',
                    price: 60000,
                  ),
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}

class ProductDetailsScreen extends StatelessWidget {
  final String name;
  final double price;

  const ProductDetailsScreen({
    super.key,
    required this.name,
    required this.price,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Product Details'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              name,
              style: const TextStyle(
                fontSize: 28,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 10),
            Text(
              'Price: ₹$price',
              style: const TextStyle(
                fontSize: 20,
              ),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: const Text('Back to Products'),
            ),
          ],
        ),
      ),
    );
  }
}

32. Passing Objects Through Navigator.push()

For larger applications, passing a complete model object is often cleaner than passing many individual values.

class Course {
  final String title;
  final String description;
  final double price;

  const Course({
    required this.title,
    required this.description,
    required this.price,
  });
}

Push the course:

final course = Course(
  title: 'Flutter Training',
  description: 'Learn Flutter application development.',
  price: 15000,
);

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => CourseDetailsScreen(
      course: course,
    ),
  ),
);

33. Returning a Boolean Result

A common use case is asking the user to confirm an action.

final confirmed = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const ConfirmationScreen(),
  ),
);

if (confirmed == true) {
  print('User confirmed');
} else {
  print('User cancelled');
}

On the confirmation screen:

ElevatedButton(
  onPressed: () {
    Navigator.pop(context, true);
  },
  child: const Text('Confirm'),
)

Cancel button:

TextButton(
  onPressed: () {
    Navigator.pop(context, false);
  },
  child: const Text('Cancel'),
)

34. Important Difference Between pop() and pushReplacement()

Consider:

[Home]
[Login]

If you call:

Navigator.pop(context);

the Login route is removed and Home becomes visible.

If you call:

Navigator.pushReplacement(
  context,
  MaterialPageRoute(
    builder: (context) => const HomeScreen(),
  ),
);

the current route is replaced with Home.

Choosing between them depends on the desired navigation history.


35. Common Mistakes with Navigator.push()

Mistake 1: Forgetting BuildContext

Navigation requires a valid BuildContext.

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const DetailsScreen(),
  ),
);

Mistake 2: Forgetting the Destination Screen

The route needs a widget to build:

builder: (context) => const DetailsScreen()

Mistake 3: Pushing the Same Screen Repeatedly

Repeatedly calling push() can create many duplicate routes in the navigation stack.

Mistake 4: Using pop() Without Considering the Stack

Before custom back navigation, you can check:

Navigator.canPop(context)

Mistake 5: Ignoring the Returned Future

If the destination screen returns information, await the Future returned by Navigator.push().


36. Navigator.push() and Async Programming

Because Navigator.push() returns a Future, it can be used with async and await.

Future openDetails() async {
  final result = await Navigator.push(
    context,
    MaterialPageRoute(
      builder: (context) => const DetailsScreen(),
    ),
  );

  print(result);
}

The Future completes when the destination route is popped.


37. Safe Context Usage After await

If an asynchronous navigation result is followed by a widget update, check that the widget is still mounted before using its context or state.

final result = await Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SelectionScreen(),
  ),
);

if (!mounted) return;

setState(() {
  selectedValue = result ?? '';
});

This helps avoid using a State object after it has been removed from the widget tree.


38. Navigator and Bottom Navigation

NavigationBar and Navigator can be used together.

For example:

Home
Products
Orders
Profile
   |
   +-- Settings
   +-- Edit Profile

The NavigationBar can switch between top-level sections, while Navigator.push() can open detail screens inside a section.

Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => const SettingsScreen(),
  ),
);

39. Navigator and Forms

Navigation is frequently used when opening forms.

ElevatedButton(
  onPressed: () async {
    final result = await Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => const EditProfileScreen(),
      ),
    );

    if (!mounted) return;

    if (result != null) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text(result),
        ),
      );
    }
  },
  child: const Text('Edit Profile'),
)

The form screen can return a message:

Navigator.pop(
  context,
  'Profile updated successfully',
);

40. Navigator and Dialogs

Some Flutter APIs such as dialogs and modal bottom sheets also use route-based mechanisms internally.

For example:

showDialog(
  context: context,
  builder: (context) {
    return AlertDialog(
      title: const Text('Delete Item?'),
      content: const Text(
        'Do you want to delete this item?',
      ),
      actions: [
        TextButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: const Text('Cancel'),
        ),
        TextButton(
          onPressed: () {
            Navigator.pop(context);
          },
          child: const Text('Delete'),
        ),
      ],
    );
  },
);

Here, Navigator.pop() dismisses the dialog route.


41. Named Routes and Navigator

Flutter supports named routes through Navigator.pushNamed().

MaterialApp(
  routes: {
    '/': (context) => const HomeScreen(),
    '/details': (context) => const DetailsScreen(),
  },
);

Navigate using:

Navigator.pushNamed(
  context,
  '/details',
);

Return using:

Navigator.pop(context);

Named routes remain part of Flutter, but current Flutter documentation does not recommend them for most new applications. For many new applications, Flutter recommends using Navigator with MaterialPageRoute or a routing package such as go_router, particularly when advanced routing or deep linking is required. Flutter Navigation and Routing


42. Navigator.push() with Named Routes

Navigator.pushNamed(
  context,
  '/profile',
);

This approach can be useful when working with an existing application that already uses named routes.

For new applications, direct route construction or a modern routing solution may be more appropriate depending on the application's requirements.


43. Nested Navigator

Large applications may contain more than one Navigator. A nested Navigator can manage navigation within a particular section or workflow.

Main Navigator
│
├── Home
├── Products
└── Profile
      │
      └── Nested Navigator
            ├── Profile Home
            ├── Edit Profile
            └── Settings

This approach can be useful when an individual section needs its own navigation history.


44. NavigatorObserver

NavigatorObserver can be used to observe navigation events such as routes being pushed or popped.

class MyNavigatorObserver extends NavigatorObserver {
  @override
  void didPush(Route route, Route? previousRoute) {
    print('Route pushed: ${route.settings.name}');
  }

  @override
  void didPop(Route route, Route? previousRoute) {
    print('Route popped: ${route.settings.name}');
  }
}

It can be registered with MaterialApp:

MaterialApp(
  navigatorObservers: [
    MyNavigatorObserver(),
  ],
  home: const HomeScreen(),
);

This can be useful for analytics, logging, and monitoring navigation behavior.


45. Practical E-Commerce Navigation Flow

Home
 |
 +-- Products
 |     |
 |     +-- Product Details
 |           |
 |           +-- Add to Cart
 |
 +-- Cart
 |     |
 |     +-- Checkout
 |           |
 |           +-- Payment
 |                 |
 |                 +-- Order Success
 |
 +-- Profile
       |
       +-- Edit Profile
       +-- Settings

Different Navigator operations can be used for different stages of this flow.

ActionNavigation Method
Open Product Detailspush()
Return to Productspop()
Login → HomepushReplacement()
Checkout → Home after completionpushAndRemoveUntil()
Return to first screenpopUntil()

46. Complete Mini Project

import 'package:flutter/material.dart';

void main() {
  runApp(const NavigationApp());
}

class NavigationApp extends StatelessWidget {
  const NavigationApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Navigator Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.blue,
        ),
        useMaterial3: true,
      ),
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Home'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'Welcome to the Application',
              style: TextStyle(
                fontSize: 22,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () async {
                final result = await Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) =>
                        const DetailsScreen(),
                  ),
                );

                if (!context.mounted) return;

                if (result != null) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(
                      content: Text(result),
                    ),
                  );
                }
              },
              child: const Text('Open Details'),
            ),
          ],
        ),
      ),
    );
  }
}

class DetailsScreen extends StatelessWidget {
  const DetailsScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Details'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'This is the Details Screen',
              style: TextStyle(fontSize: 20),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Navigator.pop(
                  context,
                  'Returned from Details Screen',
                );
              },
              child: const Text('Return Data'),
            ),
            const SizedBox(height: 10),
            TextButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: const Text('Go Back'),
            ),
          ],
        ),
      ),
    );
  }
}

47. Execution Flow of the Mini Project

  1. The application starts on HomeScreen.
  2. The user taps Open Details.
  3. Navigator.push() adds DetailsScreen to the stack.
  4. The Details screen appears.
  5. The user can press Go Back.
  6. Navigator.pop() removes the Details route.
  7. The user can also press Return Data.
  8. Navigator.pop(context, result) returns a String.
  9. The Future returned by Navigator.push() receives that String.
  10. The Home screen displays the returned result.

48. Important Navigator Methods

MethodDescription
push()Adds a route to the navigation stack.
pop()Removes the current route.
pushReplacement()Replaces the current route with another route.
pushAndRemoveUntil()Pushes a route and removes previous routes according to a condition.
popUntil()Pops routes until a condition is satisfied.
pushNamed()Pushes a route using a registered route name.
canPop()Checks whether the Navigator can pop a route.
of()Retrieves the nearest Navigator.

49. Common Interview Questions

Q1. What is Navigator in Flutter?

Navigator is a widget that manages a stack of routes and provides methods for navigating between screens.

Q2. What does Navigator.push() do?

It adds a new route to the Navigator stack and displays it above the current route.

Q3. What does Navigator.pop() do?

It removes the current route from the stack and reveals the previous route.

Q4. What is MaterialPageRoute?

It is a route implementation that provides Material-style page transitions.

Q5. Can Navigator.pop() return data?

Yes. A value can be supplied as the second argument:

Navigator.pop(context, result);

Q6. Can Navigator.push() receive returned data?

Yes. Navigator.push() returns a Future that completes when the route is popped.

Q7. What is the difference between push() and pop()?

push() adds a route, while pop() removes the current route.

Q8. What is pushReplacement()?

It replaces the current route with a new route.

Q9. What is pushAndRemoveUntil()?

It pushes a new route and removes previous routes until the supplied condition is satisfied.

Q10. What is popUntil()?

It removes routes until a specified condition becomes true.


50. Practice Exercises

  1. Create a Home screen and Details screen.
  2. Navigate from Home to Details using Navigator.push().
  3. Return to Home using Navigator.pop().
  4. Create a Product model and pass it to Product Details.
  5. Return a selected product from a selection screen.
  6. Create Login and Home screens and use pushReplacement().
  7. Create Home, Cart, Checkout, and Success screens.
  8. Use pushAndRemoveUntil() after checkout.
  9. Use popUntil() to return to the Home screen.
  10. Implement a confirmation screen that returns a Boolean result.
  11. Create a reusable navigation helper method.
  12. Experiment with MaterialPageRoute and CupertinoPageRoute.
  13. Implement a nested Navigator for a Profile section.
  14. Add a NavigatorObserver to log route changes.

51. Best Practices

  • Use Navigator.push() when adding a new screen to the navigation stack.
  • Use Navigator.pop() to return to the previous screen.
  • Use meaningful route structures for larger applications.
  • Pass strongly typed objects when practical.
  • Use generic types when returning values from routes.
  • Use pushReplacement() when the current route should no longer remain in the normal back stack.
  • Use pushAndRemoveUntil() for flows where previous routes should be removed.
  • Use popUntil() when returning to a specific point in the navigation stack.
  • Check mounted after awaiting navigation results before updating widget state.
  • Use nested navigation when a subsection requires an independent navigation flow.
  • For complex deep-linking requirements, consider Router-based navigation or a routing package such as go_router.

52. Quick Revision

ConceptMeaning
NavigatorManages navigation routes as a stack.
RouteRepresents a screen/page in navigation.
push()Adds a new route.
pop()Removes the current route.
pushReplacement()Replaces the current route.
pushAndRemoveUntil()Pushes a route and removes previous routes according to a condition.
popUntil()Pops routes until a condition is met.
MaterialPageRouteMaterial-style route.
CupertinoPageRouteCupertino-style route.
canPop()Checks whether the current Navigator can pop.
pushNamed()Pushes a registered named route.

53. Key Takeaways

  • Flutter uses a Navigator to manage a stack of routes.
  • Navigator.push() opens a new screen by adding a route to the stack.
  • Navigator.pop() closes the current screen by removing its route.
  • A route can return data through Navigator.pop(context, result).
  • The Future returned by Navigator.push() can receive that returned result.
  • MaterialPageRoute is commonly used for Material applications.
  • CupertinoPageRoute provides Cupertino-style transitions.
  • pushReplacement() is useful when replacing the current navigation entry.
  • pushAndRemoveUntil() is useful for clearing previous navigation history according to a condition.
  • popUntil() can return to an earlier point in the navigation stack.
  • Navigator can be combined with bottom navigation and nested navigation.
  • For advanced navigation and deep-linking requirements, Flutter supports Router-based navigation and routing packages.

54. Useful Resources

Flutter Navigation and Routing: Official Flutter Navigation Documentation

Navigator API: Official Navigator API Documentation

Navigate to a New Screen and Back: Flutter Navigation Basics

Passing Data: Send Data to a New Screen

Returning Data: Return Data from a Screen

JustAcademy Flutter Training: JustAcademy Flutter Training Course

Register for Flutter Course Demo: Register for Flutter Course Demo

whatsapp